import errno
import logging
import queue
import select
import signal
import socket

import epoll_config
from socket_is_remote_alive import is_remote_alive
from socket_utils import EVENTMASKS_R, mask

LOG = logging.getLogger(__name__)


class EchoServer:
    def __init__(self):
        LOG.info('创建 SOCKET 对象')
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 地址复用
        LOG.info('服务器：%d，绑定地址：%r', self.sock.fileno(), epoll_config.ADDRESS)
        self.sock.bind(epoll_config.ADDRESS)
        self.sock.listen(10)
        self.sock.setblocking(False)

        self.sockets = {self.sock.fileno(): self.sock}
        self.queue = {}
        # 1. 创建 EPOLL 对象（事件监听工具）
        self.notifier = select.epoll()
        # 2. 注册事件（读）
        self.notifier.register(self.sock.fileno(), select.EPOLLIN)

    def client_close(self, fd):
        LOG.debug('[%d] 客户端连接关闭 1: unregister', fd)
        self.notifier.unregister(fd)
        LOG.debug('[%d] 客户端连接关闭 2: sock.close', fd)
        self.sockets[fd].close()
        del self.sockets[fd]

    def client_conn(self):
        conn, address = self.sock.accept()
        LOG.info('[%d] 新连接：%r', conn.fileno(), address)
        conn.setblocking(False)
        self.notifier.register(conn.fileno(), select.EPOLLIN)
        self.sockets[conn.fileno()] = conn
        self.queue[conn] = queue.Queue()

    def start(self):
        try:
            while epoll_config.RUNING:
                # 获取 EPOLL 事件
                events = self.notifier.poll(epoll_config.TIMEOUT)
                if not events:
                    LOG.info('EPOLL 无事件，重新轮询...')
                    continue
                # LOG.info('EPOLL 事件数量：%s，开始处理...', len(events))
                for fd, event in events:
                    LOG.info('[%d] event: %r', fd, [EVENTMASKS_R.get(i, i) for i in mask(event)])
                    sock = self.sockets[fd]
                    if sock == self.sock:  # 新连接
                        self.client_conn()
                        continue

                    connected = is_remote_alive(sock)
                    LOG.info('[%d] %s, %r', sock.fileno(), sock, connected)
                    if not connected:
                        self.client_close(fd)
                        continue

                    # try:
                    #     data = sock.send(b'\0')
                    #     LOG.info('send: %r', data)
                    # except ConnectionResetError as error:
                    #     self.client_close(fd)
                    #     continue

                    if event & select.EPOLLERR:
                        LOG.info('[%d] error', sock.fileno())
                        self.client_close(fd)
                    elif event & select.EPOLLHUP:  # 关闭
                        LOG.info('[%d] close', sock.fileno())
                        self.client_close(fd)
                    elif event & select.EPOLLIN:  # 读
                        data = sock.recv(1024)
                        if data:
                            LOG.debug('[%d] 收到来自客户端 %r 的数据：%s', fd, sock.getpeername(), data)
                            self.queue[sock].put(data)
                            self.notifier.modify(fd, select.EPOLLOUT)
                    elif event & select.EPOLLOUT:  # 写
                        try:
                            msg = self.queue[sock].get_nowait()
                        except queue.Empty:
                            LOG.debug('[%d] 队列空：%r', fd, sock.getpeername())
                            self.notifier.modify(fd, select.EPOLLIN)
                        else:
                            LOG.debug('[%d] 往客户端 %r 发送数据：%r', fd, sock.getpeername(), msg)
                            sock.send(msg)
                    else:
                        LOG.error('[%d] wtf!', fd)
        except Exception as error:
            LOG.exception(error)
        finally:
            self.close()

    def close(self):
        self.notifier.unregister(self.sock.fileno())
        self.notifier.close()
        self.sock.close()


def main():
    EchoServer().start()


if __name__ == '__main__':
    main()
